package service

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"gorm.io/gorm"

	mchApi "wxpay/pkg/wechat_api"
)

type profitService struct {
	d dao.DataStore
}

func NewProfitService(d dao.DataStore) *profitService {
	return &profitService{d: d}
}

// 添加分账方
func (s *profitService) AddReceiver(ctx context.Context, profitReceiverDTO *dto.ProfitReceiverDTO) (err error) {
	const op merrors.Op = "profitService.AddReceiver"
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, profitReceiverDTO.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}
	receiveAddReq := mchApi.ReceiversAddRequest{
		AppID:        profitReceiverDTO.AppID,
		Type:         profitReceiverDTO.Type,
		Account:      profitReceiverDTO.Account,
		Name:         profitReceiverDTO.Name,
		RelationType: profitReceiverDTO.RelationType,
	}
	_, err = client.ReceiversAdd(ctx, receiveAddReq)
	if err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	pfs := dto.AdaptProfitReceiver(profitReceiverDTO)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.ProfitReceiver().Save(ctx, tx, &pfs); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 分账 幂等可重入
func (s *profitService) OrderSubmit(ctx context.Context, profitDTO *dto.ProfitOrderDTO) (err error) {
	const op merrors.Op = "profitService.OrderSubmit"
	// 构造交易对象
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), profitDTO.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 交易不存在
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return
	}
	if !trade.DoesBelongTo(profitDTO.BrandID) {
		return merrors.E(op, merrors.Permission, "无权查看")
	}

	pftOrder, err := s.d.ProfitShare().QueryByOutOrderNo(ctx, s.d.GetTx(ctx), profitDTO.OutOrderNo)
	if err == nil {
		// 已存在直接返回
		profitDTO = dto.ConvertProfitShare(pftOrder)
		return
	}
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则创建
		return s.submitOrder(ctx, profitDTO, trade)
	}
	return err
}

func (s *profitService) submitOrder(ctx context.Context, profitDTO *dto.ProfitOrderDTO, trade *model.Trade) (err error) {
	const op merrors.Op = "profitService.submitOrder"
	// 调用微信接口检查剩余可分账金额
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}

	r, err := client.ProfitShareUnSplitAmountQuery(ctx, mchApi.ProfitShareUnSplitAmountQueryRequest{TransactionID: util.ConvertNullString(trade.WxTransID)})
	if err != nil {
		const op1 merrors.Op = "profitService.submitOrder.UnSplitAmountQuery"
		return merrors.E(op1, merrors.Invalid, merrors.ErrWxApi, err)
	}
	if int64(r.UnSplitAmount) < profitDTO.GetTotalShareAmount() {
		// 剩余可分账金额不足
		return merrors.E(op, merrors.Invalid, merrors.ErrRemainNotEnough)
	}
	// 调用api分账
	receivers := make([]mchApi.ReceiverInProfitShareRequest, len(profitDTO.Receivers))
	for i, r := range profitDTO.Receivers {
		receivers[i] = mchApi.ReceiverInProfitShareRequest{
			Type:         r.Type,
			Account:      r.Account,
			Amount:       uint(r.Amount),
			Description:  r.Description,
			ReceiverName: r.ReceiverName,
		}
	}
	shareReq := mchApi.ProfitShareApplyRequest{
		SpAppID:       trade.AppID,
		SubMchID:      trade.SubMchID,
		TransactionID: trade.WxTransID.String,
		OutOrderNo:    profitDTO.OutOrderNo,
		Receivers:     receivers,
		Finish:        profitDTO.Finish,
	}
	resp, err := client.ProfitShareApply(ctx, shareReq)
	if err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	// 分账记录落地
	profitDTO.AppID = trade.AppID
	profitDTO.MchID = trade.MchID
	profitDTO.SubMchID = trade.SubMchID
	profitDTO.WxTransID = trade.WxTransID.String
	profitDTO.WxOrderID = resp.OrderID
	pfs := dto.AdaptProfitShare(profitDTO)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.ProfitShare().Create(ctx, tx, &pfs); e != nil {
			return e
		}
		// 建立分账查询任务
		if e := s.createProfitOrderQueryTask(ctx, tx, &pfs); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

func (s *profitService) createProfitOrderQueryTask(ctx context.Context, tx *gorm.DB, pfs *model.Profitshare) error {
	payload := model.NewProfitOrderQueryTask(pfs)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicProfitOrderQuery, ps, time.Minute)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 分账回退，幂等可重入
func (s *profitService) ReturnSubmit(ctx context.Context, profitDTO *dto.ProfitReturnDTO) (err error) {
	r, fErr := s.d.ProfitReturn().QueryByOutReturnNo(ctx, s.d.GetTx(ctx), profitDTO.OutReturnNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则创建
		return s.submitReturn(ctx, profitDTO)
	}
	if fErr != nil {
		return fErr
	}
	// 已存在申请，直接返回
	profitDTO = dto.ConvertProfitReturn(r)
	return
}

func (s *profitService) submitReturn(ctx context.Context, profitDTO *dto.ProfitReturnDTO) (err error) {
	const op merrors.Op = "profitService.submitReturn"
	db := s.d.GetTx(ctx)
	// 构造分账对象
	pftOrder, err := s.d.ProfitShare().QueryByOutOrderNo(ctx, db, profitDTO.OutOrderNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 分账单不存在
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "分账单不存在")
	}
	if err != nil {
		return
	}
	if !pftOrder.DoesBelongTo(profitDTO.BrandID) {
		return merrors.E(op, merrors.Permission, "无权查看")
	}
	// 调用微信分账回退接口
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, pftOrder.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}
	// 调用api分账
	returnReq := mchApi.ProfitReturnApplyRequest{
		SubMchID: pftOrder.SubMchID,
		OrderID:     util.ConvertNullString(pftOrder.WxOrderID),
		OutOrderNo:  pftOrder.OutOrderNo,
		OutReturnNo: profitDTO.OutReturnNo,
		ReturnMchID: profitDTO.ReturnMchID,
		Amount:      uint(profitDTO.Amount),
		Description: profitDTO.Description,
	}
	fmt.Println()
	fmt.Printf("==ProfitReturnApply.req== %+v", returnReq)
	fmt.Println()
	resp, err := client.ProfitReturnApply(ctx, returnReq)
	if err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	// 分账回退记录落地
	profitDTO.AppID = pftOrder.AppID
	profitDTO.MchID = pftOrder.MchID
	profitDTO.SubMchID = pftOrder.SubMchID
	profitDTO.WxOrderID = util.ConvertNullString(pftOrder.WxOrderID)
	profitDTO.WxReturnNo = resp.ReturnNo
	pfs := dto.AdaptProfitReturn(profitDTO)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.ProfitReturn().Create(ctx, tx, &pfs); e != nil {
			return e
		}
		// 建立分账回退查询任务
		if e := s.createProfitReturnQueryTask(ctx, tx, &pfs); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

// 建立分账回退查询任务
func (s *profitService) createProfitReturnQueryTask(ctx context.Context, tx *gorm.DB, pftReturn *model.Profitreturn) error {
	payload := model.NewProfitReturnQueryTask(pftReturn)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicProfitReturnQuery, ps, time.Minute)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 分账完结
func (s *profitService) FinishSubmit(ctx context.Context, profitDTO *dto.ProfitOrderDTO) (err error) {
	const op merrors.Op = "profitService.FinishSubmit"
	// 构造交易对象
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), profitDTO.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 交易不存在
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return
	}
	if !trade.DoesBelongTo(profitDTO.BrandID) {
		return merrors.E(op, merrors.Permission, "无权查看")
	}
	// 是否存在分账中订单
	orders, err := s.d.ProfitShare().QueryByTrans(ctx, s.d.GetTx(ctx), util.ConvertNullString(trade.WxTransID))
	if err != nil {
		return err
	}
	for i := range orders {
		if !orders[i].IsFinalState() {
			// 只要有一个订单处于非终态就不能进行分账完结
			return merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid)
		}
	}
	// 调用微信接口进行分账完结
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}
	req := mchApi.ProfitShareFinishRequest{
		SubMchID:      trade.SubMchID,
		TransactionID: util.ConvertNullString(trade.WxTransID),
		OutOrderNo:    profitDTO.OutOrderNo,
		Description:   profitDTO.Description,
	}
	resp, err := client.ProfitShareFinish(ctx, req)
	if err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	// 分账记录落地
	profitDTO.AppID = trade.AppID
	profitDTO.SubMchID = trade.SubMchID
	profitDTO.WxTransID = util.ConvertNullString(trade.WxTransID)
	profitDTO.WxOrderID = resp.OrderID
	pfs := dto.AdaptProfitShare(profitDTO)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.ProfitShare().Create(ctx, tx, &pfs); e != nil {
			return e
		}
		// 建立分账查询任务
		if e := s.createProfitOrderQueryTask(ctx, tx, &pfs); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

// 查询订单剩余待分账金额
func (s *profitService) UnSplitAmountQuery(ctx context.Context, profitDTO *dto.ProfitOrderDTO) (amount *dto.UnSplitAmountDTO, err error) {
	const op merrors.Op = "profitService.FinishSubmit"
	// 构造交易对象
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), profitDTO.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 交易不存在
		return nil, merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return
	}
	if !trade.DoesBelongTo(profitDTO.BrandID) {
		return nil, merrors.E(op, merrors.Permission, "无权查看")
	}
	// 调用微信接口进行剩余待分金额查询
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	req := mchApi.ProfitShareUnSplitAmountQueryRequest{TransactionID: util.ConvertNullString(trade.WxTransID)}
	resp, err := client.ProfitShareUnSplitAmountQuery(ctx, req)
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	return &dto.UnSplitAmountDTO{UnSplitAmount: int64(resp.UnSplitAmount)}, nil
}

// 分账查询同步
func (s *profitService) ProfitOrderSync(ctx context.Context, outOrderNo string) (err error) {
	// 调用分账查询接口查询分账单状态并同步，如果查到的状态非终态需要重新建立定时任务
	const op merrors.Op = "profitService.ProfitOrderSync"
	pftOrder, err := s.d.ProfitShare().QueryByOutOrderNo(ctx, s.d.GetTx(ctx), outOrderNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 分账不存在
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "分账单不存在")
		return
	}
	if err != nil {
		return
	}
	if pftOrder.IsFinalState() {
		// 已经是最终状态，直接返回
		return nil
	}
	// 查询微信分账单状态
	query := mchApi.ProfitShareQueryRequest{
		SubMchID:      pftOrder.SubMchID,
		TransactionID: pftOrder.WxTransID,
		OutOrderNo:    pftOrder.OutOrderNo,
	}
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, pftOrder.MchID)
	if err != nil {
		return
	}
	resp, err := client.ProfitShareQuery(ctx, query)
	if err != nil {
		err = merrors.E(op, merrors.Invalid, merrors.ErrWxApi, "微信支付查询分账状态失败")
		return
	}
	pftOrderDto := dto.ProfitOrderNotify{
		FinishAmount: int64(resp.FinishAmount),
		Status:       resp.Status,
		CloseReason:  resp.CloseReason,
	}
	switch resp.Status {
	case model.ProfitOrderStatusFinished:
		err = s.syncOrderFinish(ctx, pftOrder, &pftOrderDto)
	case model.ProfitOrderStatusClosed:
		err = s.syncOrderClose(ctx, pftOrder, &pftOrderDto)
	case model.ProfitOrderStatusProcessing:
		err = s.syncOrderProcessing(ctx, pftOrder)
	}
	return
}

func (s *profitService) syncOrderFinish(ctx context.Context, pftOrder *model.Profitshare, syncDto *dto.ProfitOrderNotify) (err error) {
	// 分账完成，更新状态，如果是分账完结还需要更新完结分账金额
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitOrder(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		// 更新到成功状态
		pftOrder.Success(syncDto.FinishAmount)
		if e := s.d.ProfitShare().SyncSuccess(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 保存消息, 通知上游业务
		if e := s.createProfitOrderNotification(ctx, tx, pftOrder); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

func (s *profitService) createProfitOrderNotification(ctx context.Context, tx *gorm.DB, pftOrder *model.Profitshare) error {
	payload := model.NewProfitOrderNotification(pftOrder)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicProfitOrderNotify, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func (s *profitService) syncOrderClose(ctx context.Context, pftOrder *model.Profitshare, syncDto *dto.ProfitOrderNotify) (err error) {
	// 分账失败, 更新状态和失败原因
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitOrder(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		// 更新到成功状态
		pftOrder.Failed(syncDto.CloseReason)
		if e := s.d.ProfitShare().SyncFailed(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 保存消息, 通知上游业务
		if e := s.createProfitOrderNotification(ctx, tx, pftOrder); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

func (s *profitService) syncOrderProcessing(ctx context.Context, pftOrder *model.Profitshare) (err error) {
	// 分账处理中
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitOrder(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		if pftOrder.IsProcessing() {
			// 不需要更新
			return nil
		}
		// 更新到处理中状态
		pftOrder.Processing()
		if e := s.d.ProfitShare().SyncProcessing(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 需要继续查询，建立分账查询任务
		if e := s.createProfitOrderQueryTask(ctx, tx, pftOrder); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

func (s *profitService) ProfitReturnSync(ctx context.Context, outReturnNo string) (err error) {
	// 调用分账查询接口查询分账单状态并同步，如果查到的状态非终态需要重新建立定时任务
	const op merrors.Op = "profitService.ProfitOrderSync"
	pftOrder, err := s.d.ProfitReturn().QueryByOutReturnNo(ctx, s.d.GetTx(ctx), outReturnNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 分账不存在
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "分账单不存在")
		return
	}
	if err != nil {
		return
	}
	if pftOrder.IsFinalState() {
		// 已经是最终状态，直接返回
		return nil
	}
	// 查询微信回退单状态
	query := mchApi.ProfitReturnQueryRequest{
		SubMchID:    pftOrder.SubMchID,
		OrderID:     pftOrder.WxOrderID,
		OutOrderNo:  pftOrder.OutOrderNo,
		OutReturnNo: pftOrder.OutReturnNo,
	}
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, pftOrder.MchID)
	if err != nil {
		return
	}
	resp, err := client.ProfitReturnQuery(ctx, query)
	if err != nil {
		err = merrors.E(op, merrors.Invalid, merrors.ErrWxApi, "微信支付查询分账回退失败")
		return
	}
	ft, err := util.ParseTimePtr(util.LayoutSecondWithTimeZoneAndT, resp.FinishTime)
	if err != nil {
		err = merrors.E(op, merrors.Invalid, merrors.ErrWxApi, "微信支付查询分账回退时间格式错误")
		return
	}
	pftOrderDto := dto.ProfitReturnNotify{
		Result:     resp.Result,
		FailReason: resp.FailReason,
		FinishTime: ft,
	}
	switch resp.Result {
	case model.ReturnResultSuccess:
		err = s.syncReturnFinish(ctx, pftOrder, &pftOrderDto)
	case model.ReturnResultFailed:
		err = s.syncReturnFailed(ctx, pftOrder, &pftOrderDto)
	case model.ReturnResultProcessing:
		err = s.syncReturnProcessing(ctx, pftOrder)
	}
	return
}

func (s *profitService) syncReturnFinish(ctx context.Context, pftOrder *model.Profitreturn, syncDto *dto.ProfitReturnNotify) (err error) {
	// 回退成功，更新状态
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitReturn(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		// 更新到成功状态
		pftOrder.Success(syncDto.FinishTime)
		if e := s.d.ProfitReturn().SyncSuccess(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 保存消息, 通知上游业务
		if e := s.createProfitReturnNotification(ctx, pftOrder, tx); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 创建分账回退状态变更通知消息
func (s *profitService) createProfitReturnNotification(ctx context.Context, pftOrder *model.Profitreturn, tx *gorm.DB) error {
	payload := model.NewProfitReturnNotification(pftOrder)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicProfitReturnNotify, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func (s *profitService) syncReturnFailed(ctx context.Context, pftOrder *model.Profitreturn, syncDto *dto.ProfitReturnNotify) (err error) {
	// 回退失败，更新状态和失败原因
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitReturn(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		// 更新到成功状态
		pftOrder.Failed(syncDto.FailReason)
		if e := s.d.ProfitReturn().SyncFailed(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 保存消息, 通知上游业务
		if e := s.createProfitReturnNotification(ctx, pftOrder, tx); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

func (s *profitService) syncReturnProcessing(ctx context.Context, pftOrder *model.Profitreturn) (err error) {
	// 回退处理中 只更新状态
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		pftOrder, e := s.d.Lock().LockProfitReturn(ctx, tx, pftOrder.ID)
		if e != nil {
			return e
		}
		if pftOrder.IsFinalState() {
			// 幂等处理
			return nil
		}
		if pftOrder.IsProcessing() {
			// 不需要更新
			return nil
		}
		// 更新到成功状态
		pftOrder.Processing()
		if e := s.d.ProfitReturn().SyncSuccess(ctx, tx, pftOrder); e != nil {
			return e
		}
		// 保存消息, 重试查询
		if e := s.createProfitReturnQueryTask(ctx, tx, pftOrder); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}
